package com.demo.task.vm

import android.util.Log
import androidx.lifecycle.ViewModel
import com.demo.task.MainApplication
import com.demo.task.bean.PlanBean
import com.demo.task.bean.Response
import com.demo.task.config.Config
import com.demo.task.config.ConfigUtils
import com.demo.task.ext.clearData
import com.demo.task.ext.delCacheJson
import com.demo.task.ext.delRes
import com.demo.task.ext.filterLocalUrls
import com.demo.task.ext.getFileUrls
import com.demo.task.ext.getResPath
import com.demo.task.ext.getSerial
import com.demo.task.ext.getUrlFileName
import com.demo.task.ext.isMoreThanMinutes
import com.demo.task.ext.isSameData
import com.demo.task.ext.isWithinDateRange
import com.demo.task.ext.isWithinTimeRange
import com.demo.task.ext.launchIO
import com.demo.task.ext.launchMain
import com.demo.task.ext.readCache
import com.demo.task.ext.toTime
import com.demo.task.manager.GpsManager
import com.demo.task.remote.ApiService
import com.demo.task.socket.RemoteInterface
import com.demo.task.socket.SocketIO
import com.demo.task.utils.DownloadUtil
import com.google.gson.Gson
import com.orhanobut.logger.Logger
import com.zed.service.APIHelper
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Job
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.json.JSONObject
import java.io.File
import java.time.Duration
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.Collections
import java.util.UUID
import javax.inject.Inject

@HiltViewModel
class MainViewModel @Inject constructor(
    private val apiService: ApiService,
    private val socketIO: RemoteInterface,
    private val downloadUtil: DownloadUtil
) : ViewModel() {

    private val idsSet by lazy { Collections.synchronizedSet(mutableSetOf<String>()) }
    private val mutex = Mutex() // 协程锁

    private var loopJob: Job? = null
    private var awaitJob: Job? = null
    private var pageId = -1

    private val _box = MutableStateFlow<List<PlanBean.Plan.Group.Page.Box>>(emptyList())
    val box: StateFlow<List<PlanBean.Plan.Group.Page.Box>> = _box

    private val _planBean = MutableStateFlow<PlanBean?>(null)
    val planBean: StateFlow<PlanBean?> = _planBean

    private val _playComplete = MutableSharedFlow<Boolean>()
    val playComplete: SharedFlow<Boolean> = _playComplete

    private val apiHelper by lazy { APIHelper.getInstance() }

    val gpsManager by lazy { GpsManager(socketIO) }

    //三个窗口素材都一致
    var isSameMedia = false
        private set

    init {
        gpsManager.activate()
        socketMsgListener()
        initSysTime()
        initUI()
        getPlan()
    }

    private fun initUI() {
        launchIO {
            ConfigUtils.control?.also {
                _planBean.emit(it)
            }

            runCatching {
                apiService.getControl()
            }.onFailure {
                Log.e(TAG, "getControl failure: ", it)
                Logger.e(it, "getControl failure: ${it.message}")
            }.onSuccess {
                it.data?.firstOrNull()?.also { control ->
                    ConfigUtils.control = control
                    _planBean.emit(control)
                }
            }
        }
    }


    private fun getPlan() = launchIO {
        Log.i(TAG, "getPlan: ")
        Logger.i("getPlan: ")
        //delay(1_000)
        //updateSysDate("2025-04-11 15:29:30")
        val bean = runCatching {
            //请求参数
            val params = mapOf(
                "rawAvailSize" to "0",//内存使用
            )

            apiService.getData(
                //sn = "ZNAP25011700003",
                sn = getSerial(),
                params = params
            )
        }.onFailure {
            Log.e(TAG, "getPlan failure: ", it)
            Logger.e(it, "getPlan failure: ${it.message}")
        }.getOrElse {
            Response<PlanBean>(null, null, null, null).apply {
                readCache()?.also {
                    data = listOf(it)
                }
            }
        }

        Logger.i(Gson().toJson(bean))

        bean.data?.firstOrNull()?.also {
            Logger.i("bean.data?.firstOrNull(): ")
            if (bean.code != 204) {
                awaitDownload(it)
                Log.i(TAG, "检查资源完成: ")
                Logger.i("检查资源完成")
                beginTask(it)
            }
        }
    }

    private suspend fun awaitDownload(bean: PlanBean) {
        val fileUrls = bean.getFileUrls()
        downloadUtil.download(fileUrls.filterLocalUrls())

        fileUrls.forEach { url ->
            val file = File(getResPath(), url.getUrlFileName())
            downloadUtil.awaitFile(file)
        }

        fileUrls.delRes()
    }

    suspend fun notifyAwait(id: String) {
        idsSet.remove(id)
        val isEmpty = idsSet.isEmpty()
        Log.i(TAG, "notifyAwait: id:$id isEmpty:$isEmpty idsSet:$idsSet")
        if (idsSet.isEmpty()) {
            getAwaitJob()?.cancel()
            if (isSameMedia) {
                _playComplete.emit(true)
            }
        }
    }

    private fun stopPlan() = launchIO {
        loopJob?.cancel()
        getAwaitJob()?.cancel()
        runBlocking {
            pageId = -1
            _box.emit(emptyList())
        }
    }

    /**
     * 监听socketIO消息
     */
    private fun socketMsgListener() {
        launchMain {
            socketIO.msg.collectLatest { msg ->
                val jsonObject = runCatching {
                    JSONObject(msg)
                }.getOrNull() ?: return@collectLatest

                jsonObject.optInt("sync").takeIf { it == 1 }?.also {
                    getPlan()
                }

                jsonObject.optInt("clearplan").takeIf { it == 1 }?.also {
                    delCacheJson()
                    stopPlan()
                }
                jsonObject.optInt("clearmedia").takeIf { it == 1 }?.also {
                    emptyList<String>().delRes()
                    stopPlan()
                }
                jsonObject.optInt("cleardata").takeIf { it == 1 }?.also {
                    MainApplication.instance.clearData()
                }
                jsonObject.optString("control").takeIf { it.isNotEmpty() }?.also {
                    runCatching {
                        Gson().fromJson(it, PlanBean::class.java)
                    }.onFailure {
                        Log.e(TAG, "socketMsgListener: ", it)
                    }.getOrNull()?.also {
                        ConfigUtils.control = it
                        _planBean.emit(it)
                    }

                }
                jsonObject.optString("plan").takeIf { it.isNotEmpty() }?.also {
                    val bean = Gson().fromJson(it, PlanBean::class.java)
                    beginTask(bean)
                }
                jsonObject.optString("testplan").takeIf { it.isNotEmpty() }?.also {
                    delCacheJson()
                    val bean = Gson().fromJson(it, PlanBean::class.java)
                    _planBean.emit(bean)
                    awaitDownload(bean)
                    Log.i(TAG, "awaitDownload: ")
                    beginTask(bean)
                }
            }
        }
    }

    private fun beginTask(planBean: PlanBean) {
        loopJob?.cancel()
        val plans = planBean.plan
        if (plans.isNullOrEmpty()) {
            runBlocking { _box.emit(emptyList()) }
            return
        }

        loopJob = launchIO {
            pageId = -1
            _box.emit(emptyList())
            delay(1_000)

            while (true) {
                for (plan in plans) {
                    Log.i(TAG, "plan id: ${plan.id}")
                    Logger.i("plan id: ${plan.id}")
                    pageId = -1
                    _box.emit(emptyList())
                    delay(100)

                    if (isWithinDateRange(plan.startDate, plan.endDate) &&
                        isWithinTimeRange(plan.startTime, plan.endTime)
                    ) {
                        Config.planId = plan.id.toString()
                        val job = launch {
                            var list = emptyList<PlanBean.Plan.Group.Page.Box>()
                            while (true) {
                                for (group in plan.group) {
                                    for (page in group.pages) {
                                        isSameMedia = page.box.isSameData()
                                        if (pageId != page.id) {
                                            list = page.box.map {
                                                it.copy(uid = UUID.randomUUID().toString())
                                            }

                                            Log.i(TAG, "send plan -> id:${plan.id} ${list.size}")
                                            Logger.i("send plan -> id:${plan.id} ${list.size}")

                                            _box.emit(list)
                                            pageId = page.id
                                        }

                                        repeat(page.repeat) { index ->
                                            Log.i(TAG, "being repeat:$index page:${page.id}")
                                            Logger.i("being repeat:$index page:${page.id}")

                                            //创建挂起
                                            setAwaitJob(launch(start = CoroutineStart.LAZY) { awaitCancellation() })
                                            idsSet.clear()
                                            list.forEach { idsSet.add(it.id) }
                                            getAwaitJob()?.join()//等待

                                            Log.i(TAG, "end repeat:$index page:${page.id}")
                                            Logger.i("end repeat:$index page:${page.id}")
                                        }

                                    }
                                }
                            }
                        }

                        //plan计时器
                        launch {
                            val currentTime = LocalDateTime.now().toLocalTime()
                            val duration = Duration.between(currentTime, plan.endTime.toTime())
                            if (!duration.isNegative) {
                                Log.i(TAG, "plan timer: ${duration.toMillis()}")
                                delay(duration.toMillis() + 1)
                                pageId = -1
                                _box.emit(emptyList())
                            }
                            job.cancel()
                        }

                        job.join()
                    }
                }
                delay(1000)
            }
        }
    }

    private fun initSysTime() = launchIO {
        val cacheTime = ConfigUtils.time
        Log.e(TAG, "initSysTime: $cacheTime")
        Logger.i("initSysTime: 初始化缓存时间 -> $cacheTime")

        if (cacheTime.isMoreThanMinutes(5)) {
            updateSysDate(cacheTime)
        }
    }

    private fun updateSysDate(serverTime: String?) {
        if (!serverTime.isNullOrEmpty()) {
            val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
            val dateTime = LocalDateTime.parse(serverTime, formatter)
            val year: Int = dateTime.year
            val month: Int = dateTime.monthValue
            val day: Int = dateTime.dayOfMonth
            val hour: Int = dateTime.hour
            val minute: Int = dateTime.minute
            val second: Int = dateTime.second
            apiHelper.setSysTime(year, month, day, hour, minute, second)
        }
    }

    private suspend fun setAwaitJob(value: Job?) {
        mutex.withLock { awaitJob = value }
    }

    private suspend fun getAwaitJob(): Job? {
        return mutex.withLock { awaitJob }
    }

    companion object {
        private const val TAG = "MainViewModel"
    }
}